JavaScript 的變數


Posted by saffran on 2020-12-01

變數宣告與 undefined

變數,就是一個「放東西的箱子」,這個箱子會取一個名稱,之後就可以用箱子的名稱去代指裡面裝的東西

宣告一個變數,例如:

var box = hello

這裡的 = 不是「等於」的意思

在 JavaScript 的 = 是「賦值」(賦予值)的意思

可以看作是 var box <= hello
代表「我要把 hello 放到 box 裡面」

注意!宣告變數不可以用「數字」開頭

例如:下面這樣是錯誤的

var 335box = 57

命名方式

變數的命名方式有分為兩種:

  1. 用底線來連接
    var api_response = 57
    
  2. 駝峰式(建議用這種)
    var apiResponse = 0
    

重點是!要統一!用了駝峰式就統一用駝峰式

變數的大小寫是有區別的

宣告變數 box 和 BOX 會是兩個不同的變數

var box = hello
var BOX = 456

變數 undefined

在 box 裡面沒有裝東西

var box
console.log(box)

會印出 undefined
「undefined」的意思是:有宣告這個變數,但是沒有給值

變數 is not defined

var box
console.log(boxkkk)

會印出 boxkkk is not defined
「is not defined」的意思是:根本沒有 boxkkk 宣告這個變數

用位元運算來判斷變數是奇數/偶數

var box = 57
console.log(box & 1)

會印出 1,因為 box 是奇數

運算子 ++--

var a = 0
a = a + 5
console.log(a)

上面程式碼的意思是:
會先執行 = 右邊的,把 a + 5 算完後的值,再放到 a 裡面

因此會印出 5

a = a + 5 也可以寫成 a += 5

兩種寫法是同樣的意思

var a = 0
a += 5
console.log(a)

a = a - 5 也可以寫成 a -= 5

如果是要 +1 或 -1,有另一種更方便的寫法

a += 1 也可以寫成 a++

a -= 1 也可以寫成 a--

因為 +1 和 -1 在 JS 裡面很常用到,所以就決定給它一個專門的運算子來用

++ 放在“後面”跟放在“前面”的差別在哪呢?

如果是 a++

var a = 0

console.log(a++ && 30)
console.log('a:', a)

印出來的結果會是:

0
a: 1

第一句 console.log(a++ && 30) 會印出 0,
是因為 console.log(a++ && 30) 這句可以看成是:

console.log(a && 30)
a++

所以,

++ 放後面的話

第一步:會先執行 console.log(a && 30) 這句(這時的 a = 0,所以會印出 0)

第二步:再執行 a++ (這時的 a 才會是 1)

如果是 ++a

var a = 0

console.log(++a && 30)
console.log('a:', a)

印出來的結果會是:

30
a: 1

第一句 console.log(++a && 30) 會印出 30,
是因為 console.log(++a && 30) 這句可以看成是:

a++
console.log(a && 30)

所以,

++ 放前面的話

第一步:會先執行 a++ (這時的 a 會是 1)

第二步:再執行 console.log(a && 30) 這句(這時的 a = 1,所以會印出 30)

變數的各種型態

在宣告變數時,雖然不用指定變數的型態,但在 JS 運作時,還是會給變數一個型態

變數型態有分幾種:

  • Primitive 基本型別
  • object, undefined, function

Primitive 基本型別

Primitive 就是「最原始的型別」,分成三種:

  1. boolean (true 或是 false)
  2. number
  3. string (用單引號 or 雙引號包起來的東西)

typeof

typeof 後面加上想看的東西,

typeof 會回傳一個字串,告訴你那個東西是什麼型別

console.log('type of true', typeof true)

會印出:

type of true boolean

object, undefined, function

  • 陣列、物件的型別都是屬於 object
  • undefined 的型別就是 undefined

注意!沒有一個型別是叫做「array」

注意!null 的型別是 object

這是從 JavaScript 早期就存在的性質,沿用到現在

console.log('type of [1]', typeof [1])
console.log('type of {a: 1}}', typeof { a: 1 })
console.log('type of null', typeof null)
console.log('type of undefined', typeof undefined)
console.log('type of function', typeof function(){})

typeof 可以得到下面回傳的結果:

type of [1] object
type of {a: 1}} object
type of null object
type of undefined undefined
type of function function

陣列(Array)

array 是一種資料結構
用來存「性質相似」的資料

物件(Object)

object 也是一種資料結構
object 的格式是:

{
  key: value,
  key: value,
  key: value
}

變數的運算

需要注意「型態」!

把字串轉型成「數字」

方式一:
Number(a) 把字串 a 轉成數字

var a = '58'
var b = 30
console.log(Number(a) + b)

方式二:
用一個 function 叫做 parseInt()

  • Int 是 integer 的意思
  • 傳入的參數 10 代表:我這個數字是「10 進位」
var a = '58'
var b = 30
console.log(parseInt(a, 10) + b)

需要注意「浮點數誤差」!

「浮點數」就是「小數」
為了避免碰到「浮點數誤差」,如果可以的話,盡量不要用到小數

例如:

var a = 0.1 + 0.2
console.log(a == 0.3)

結果竟然是回傳 false

var a = 0.1 + 0.2
console.log(a == 0.3)
console.log(a)

console.log(a) 來印出 a 的值,竟然是 0.30000000000000004,而不是 0.3

原因是:
電腦在存「小數」時,沒辦法存的那麼精準(會有一些誤差)
因此,我存了 0.2 這個數字,但在電腦裡可能其實是 0.2000000000000003

有些小數可以存的很精準,有些卻不行

更多關於浮點數的說明可參考 [CS101]初心者的計概與 coding 火球術 - 浮點數誤差、數字在電腦是怎麼儲存的

=====

一個等號: = 是「賦值」的意思

var a = 10

console.log(a = 5)

如果在 console.log() 裡面只放入一個等號(不小心打錯了,少打一個等號),結果就是回傳 5

var a = 20
console.log(a = 5)
// output: 5

因為它的執行順序會是:

  1. 先執行 a = 5
  2. 再執行 console.log(a)

執行順序:從右執行到左

var a = 20 == 20
console.log(a)
// output: true

會回傳 true 是因為:它的執行順序會是「從右執行到左」

  1. 先執行 20 == 20,先判斷 20 是否等於 20(是 true)
  2. 所以就是 var a = true
  3. 所以 console.log(a) 就會印出 true

但建議還是自己加上括號去決定它的執行順序:

var a = (20 == 20)
console.log(a)

===== 的差異

== 不會「比較兩個東西的型態」

兩個等號: == 就是「判斷這兩個東西是不是相等」,但不會去管型態

var a = 20
console.log(a == 20)
// output: true
console.log(30 == '30')
//output: true

可以把「空字串」看成是「0」

console.log(0 == '')
// output: true

=== 會「比較兩個東西的型態」

三個等號: === 就是「判斷這兩個東西是不是相等」,且會去比較型態

number 不等於 string

console.log(30 === '30')
// output: false
console.log(0 === '')
// output: false

建議:永遠都用 === 來判斷,這樣最不容易出錯

如果只有使用 ==,可能會因為沒有判斷出型態不相同而出現問題
例如:console.log(30 + '30') 會等於 3030 而不是 60

從「object 的等號」真正理解變數

補充教學文章 從博物館寄物櫃理解變數儲存模型

console.log([] === []) // output: false
console.log([1] === [1]) // output: false
console.log({} === {}) // output: false
console.log({a: 1} === {a: 1}) // output: false

以上的判斷結果都會是 false,為什麼呢?

注意!在做題目的時後,當我要判斷「陣列 arr 是否為一個空陣列」
這是錯誤寫法:
因為就算 arr 是一個空陣列,跟小括號裡面的 [] 也會是不同的兩個空陣列(在兩個不同的記憶體位置上)

  if(arr === []){
    return 'empty'
  }

這是正確寫法:
用「陣列長度是否等於 0」來判斷「arr 是否為空陣列」

  if(arr.length === 0){
    return 'empty'
  }

先從 primitive 型別說起


在變數裡面放入一個 number,例如: var a = 20
a === 20 就會是 true 沒錯
因為:變數的箱子裡面放的就是「20」這個值

var a = 20
console.log(a === 20)
// output: true

只要變數是屬於 primitive 型別的(包括 number, string, boolean),因為都是直接存「值」,所以在判斷是否相等時,只會去比較「值」

這裡以 number 為例:

  • 宣告一個變數 a = 3
  • 宣告另一個變數 b,讓 b = a
  • 這時,a === b 就是 true(因為 a, b 的值都等於 3)
    var a = 3
    var b = a
    console.log('a:', a, 'b:', b)
    console.log(a === b)
    
    output:
    a: 3 b: 3
    true
    
    現在,我把 b 的值改成 8
  • 修改 b 的值,並不會改到 a 的值
  • 這時,a === b 就是 false(因為 a, b 的值不相等了)
    var a = 3
    var b = a
    b = 8
    console.log('a:', a, 'b:', b)
    console.log(a === b)
    
    output:
    a: 3 b: 8
    false
    
    總結:

當「變數」裡面裝的是 number, string, boolean 這些 primitive 的型別時,會有這三種特性(特性比較單純、直覺):

1. 變數裡面存的就是「值」,而不是去存「記憶體位置」

例如:var a = 3 在變數 a 裡面存的就是「3」這個值

2. 因為變數裡面存的是「值」,所以在判斷「兩個變數」是否相等時,只會去比較「值」。只要「兩個變數的值」相等,兩個變數就相等

3. 就算「變數的值」相等,但每個變數都是獨立的,會在「不同的記憶體位置」上,所以當我修改一個變數時,無論如何都不會去改到另一個變數


但是,對於「陣列、物件」來說,卻不是這樣子

當變數的型別是 object(陣列、物件)時,因為存的是「記憶體位置」,因此,在判斷「兩個陣列/兩個物件」是否相等時,不是去比較「值」,而是去比較「記憶體位置」

var obj = {
  a: 1
}
console.log(obj === {a: 1})
// output: false

下圖是錯的:
在物件 obj 的箱子裡面,放的並不是 {a: 1} 這個值

其實,在 JavaScript 運作的底層是這樣的(陣列、物件都是這樣,因為 typeof 陣列、物件 的型別都是 object):

注意!下面說明的情況,不會發生在「number, string, boolean」這三種屬於 primitive 的型別

var obj = {a: 1}

當我宣告變數 obj 等於一個物件 {a: 1} 時,它會先把物件放在某個地方叫做「記憶體位置」(是一個真正在電腦裡的記憶體位置,例如這裡假設的 0x01

obj 裡面存的東西其實是這個「記憶體位置 0x01」,而不是直接存 {a: 1} 這個物件

我們沒有任何方法可以得知這個「記憶體位置」是哪裡,因為在使用 JavaScript 時,它就會直接把「這個記憶體位置上面的東西 {a: 1}」給我

假設,我現在再宣告了另一個變數: obj2

var obj = {a: 1}
var obj2 = {a: 1}

儘管兩個物件存的“數值”是一樣的(都是 a: 1),但是 obj2 會再建立一個「新的記憶體位置」(這裡假設是 0x05

因為 JavaScript 在做判斷時,比較的是「記憶體位置」。所以,obj 不會等於 obj2,因為「兩個物件存的記憶體位置是不同的」(兩個箱子裡面放的是不同的記憶體位置)

讓兩個物件相等:指向同一個記憶體位置(改一個,也同時改到另一個)

怎麼做才會讓兩個物件相等?
我讓 obj = obj2,這樣兩個物件就會相等了

var obj = {
  a: 1
}
var obj2 = obj
console.log(obj === obj2)
// output: true

當我改變 obj2.a 的值(讓 obj2.a = 50),也會同時改到 obj.a 的值

var obj = {
  a: 1
}
var obj2 = obj
obj2.a = 50

console.log('obj', obj)
console.log('obj2', obj2)
console.log(obj === obj2)

output:

obj { a: 50 }
obj2 { a: 50 }
true

原因是:
這也跟「記憶體位置」有關

因為此時的 objobj2,裡面的東西是「指向同一個記憶體位置」

兩個物件會相等的原理

我原本有一個變數 obj,裡面存的記憶體位置是 0x01

當我宣告 var obj2 = obj,背後做的事情是:

宣告一個變數 obj2,指向同一個物件 {a: 1}(所以記憶體位置也是存 0x01

因此,這也是為什麼 obj === obj2 會是 true(因為是指向同一個記憶體位置、同一個物件)

這時,當我執行到 obj2.a = 50,背後做的事情是:針對「同一個記憶體位置」上的物件,去新增、修改屬性

. 的方式去新增、修改屬性,例如:在 obj2 新增一個 obj2.b = 200obj 也會一起被新增 obj.b = 200

會改到 0x01 這個記憶體位置裡面的 a,因此,在改 obj2.a 的同時,也會一起改到 obj.a

讓兩個物件不相等:指向不同的記憶體位置(改一個,並不會影響到另一個)

當我「用 =」讓 obj2 等於一個新的物件 {b: 1},背後做的事情是:obj2 會指向一個新的記憶體位置

obj2 就會斷開原本跟 obj 的連結,並建立另一個新的記憶體位置 0x05,因此 obj 就不會跟 obj2 相等了(是兩個不同的物件)

var obj = {
  a: 1
}
var obj2 = obj
obj2.a = 50
obj2 = {b: 1}

console.log('obj', obj)
console.log('obj2', obj2)
console.log(obj === obj2)

output:

obj { a: 50 }
obj2 { b: 1 }
false

#javascript







Related Posts

ecto 簡介 (1) – cell 與 plasm

ecto 簡介 (1) – cell 與 plasm

[Note] React - Hooks: useReducer

[Note] React - Hooks: useReducer

[ React筆記 ] 使用ReactDOM製作一個Modal

[ React筆記 ] 使用ReactDOM製作一個Modal


Comments